using System;
using System.Collections.Generic;
using System.Text;
using Bloomberglp.Blpapi;
using System.Diagnostics;

namespace TALoaders
{
    class BloombergLoader : BaseLoader
    {
        #region Private Members
        private static readonly Name FIELD_ID = new Name("fieldId");
        private static readonly Name SECURITY_DATA = new Name("securityData");
        private static readonly Name SECURITY_NAME = new Name("security");
        private static readonly Name FIELD_DATA = new Name("fieldData");
        private static readonly Name DATE = new Name("date");
        private static readonly Name RESPONSE_ERROR = new Name("responseError");
        private static readonly Name SECURITY_ERROR = new Name("securityError");
        private static readonly Name FIELD_EXCEPTIONS = new Name("fieldExceptions");
        private static readonly Name ERROR_INFO = new Name("errorInfo");
        private static readonly Name CATEGORY = new Name("category");
        private static readonly Name MESSAGE = new Name("message");

        private Service refDataServiceM;
        private Session sessionM;
        static private readonly TraceSwitch traceM = new TraceSwitch("BloombergLoader", "Debug essages in BloombergLoader");
        #endregion

        #region Public Methods
        public BloombergLoader()
            : base(Loader.Bloomberg)
        {
            SessionOptions sessionOptions = new SessionOptions();
            sessionOptions.ServerHost = "localhost";
            sessionOptions.ServerPort = 8194;

            this.sessionM = new Session(sessionOptions);
            bool sessionStarted = sessionM.Start();
            if (!sessionStarted)
                throw new Exception("BloombergLoader: Failed to start session");
            if (!sessionM.OpenService("//blp/refdata"))
                throw new Exception("BloombergLoader: Failed to open //blp/refdata");

            this.refDataServiceM = sessionM.GetService("//blp/refdata");
        }
        override public SortedList<DateTime, Quote> getHistoricalPrices(string ticker, DateTime startDate, DateTime endDate)
        {
            Debug.WriteLineIf(traceM.TraceInfo, "--> Calling BloombergLoader.getHistoricalPrices(" + ticker + ", " + startDate.ToShortDateString() + ", " + endDate.ToShortDateString() + ")");
            Request request = this.refDataServiceM.CreateRequest("HistoricalDataRequest");
            Element securities = request.GetElement("securities");
            securities.AppendValue(ticker);
            Element fields = request.GetElement("fields");
            fields.AppendValue(this.quoteToBloombergField(QuoteType.Open));
            fields.AppendValue(this.quoteToBloombergField(QuoteType.Low));
            fields.AppendValue(this.quoteToBloombergField(QuoteType.High));
            fields.AppendValue(this.quoteToBloombergField(QuoteType.Close));
            fields.AppendValue(this.quoteToBloombergField(QuoteType.Volume));

            request.Set("periodicityAdjustment", "ACTUAL");
            request.Set("periodicitySelection", "DAILY");
            request.Set("startDate", startDate.ToString("yyyyMMdd"));
            request.Set("endDate", endDate.ToString("yyyyMMdd"));
            TimeSpan diff = endDate - startDate;
            request.Set("maxDataPoints", diff.Days);
            request.Set("returnEids", true);

            this.sessionM.SendRequest(request, null);

            // Parse the mesage tree
            SortedList<DateTime, Quote> result = new SortedList<DateTime, Quote>();
            while (true)
            {
                Event eventObj = sessionM.NextEvent();
                // process message
                foreach (Message msg in eventObj.GetMessages())
                {
                    if (msg.MessageType.Equals(Bloomberglp.Blpapi.Name.GetName("HistoricalDataResponse")))
                    {
                        // process errors
                        Debug.WriteLineIf(traceM.TraceVerbose, "Processing msg=" + msg);
                        if (msg.HasElement(RESPONSE_ERROR))
                        {
                            Element error = msg.GetElement(RESPONSE_ERROR);
                            throw new Exception("Bloomberg Response Error: " + error.GetElementAsString(MESSAGE));
                        }
                        // No response error

                        Element secDataArray = msg.GetElement(SECURITY_DATA);
                        if (secDataArray.HasElement(SECURITY_ERROR))
                        {
                            // security error
                            Element secError = secDataArray.GetElement(SECURITY_ERROR);
                            throw new Exception("Bloomberg Security error:" + secError.GetElementAsString(MESSAGE));
                        }
                        // process securities data
                        foreach (Element fieldData in secDataArray.Elements)
                        {
                            if (fieldData.Name.ToString() == "fieldData")  // fieldData is an array
                            {
                                for (int pointIndex = 0; pointIndex < fieldData.NumValues; pointIndex++) // iterate over each fieldData array element
                                {
                                    Element eFields = fieldData.GetValueAsElement(pointIndex);
                                    Debug.WriteLineIf(traceM.TraceVerbose, "Evaluating eFields=" + eFields);
                                    if (eFields.HasElement("date") && eFields.HasElement("PX_OPEN") &&
                                        eFields.HasElement("PX_HIGH") && eFields.HasElement("PX_LOW") &&
                                        eFields.HasElement("PX_LAST"))
                                    {
                                        DateTime date = Convert.ToDateTime(eFields.GetElement("date").GetValueAsString());
                                        double open = eFields.GetElement("PX_OPEN").GetValueAsFloat64();
                                        double high = eFields.GetElement("PX_HIGH").GetValueAsFloat64();
                                        double low = eFields.GetElement("PX_LOW").GetValueAsFloat64();
                                        double close = eFields.GetElement("PX_LAST").GetValueAsFloat64();
                                        double volume = 0;
                                        if (eFields.HasElement("PX_VOLUME")) // Note: Indexes often do not have volume information
                                            volume = eFields.GetElement("PX_VOLUME").GetValueAsFloat64();
                                        double adjClose = close;    // Bloomberg adjusts close
                                        Quote quote = new Quote(open, high, low, close, volume, adjClose);
                                        result.Add(date, quote);
                                    }
                                    else if (eFields.HasElement("date") && eFields.HasElement("PX_LAST"))
                                        // Workaround for days with missing data. not very nice
                                    {
                                        DateTime date = Convert.ToDateTime(eFields.GetElement("date").GetValueAsString());
                                        double close = eFields.GetElement("PX_LAST").GetValueAsFloat64();
                                        Quote quote = new Quote(close, close, close, close, 0, close);
                                        result.Add(date, quote);
                                    }
                                    else
                                    {
                                        Debug.WriteLineIf(traceM.TraceError, "Missing fields: date=" + eFields.HasElement("date"));
                                    }
                                }
                            }
                        } // end foreach

                    } // end if
                }
                if (eventObj.Type == Event.EventType.RESPONSE)
                {
                    break;
                }
            }
            Debug.WriteLineIf(traceM.TraceInfo, " ... Loaded " + result.Count + " quotes");
            return result;
        }
        override public double getLastPrice(string ticker, out string timeStamp)
        {
            double result = -1;
            timeStamp = "#NA";

            Request request = this.refDataServiceM.CreateRequest("ReferenceDataRequest");
            Element securities = request.GetElement("securities");
            securities.AppendValue(ticker);
            Element fields = request.GetElement("fields");
            fields.AppendValue("PX_LAST");
            fields.AppendValue("LAST_UPDATE");


            sessionM.SendRequest(request, null);

            while (true)
            {
                Event eventObj = sessionM.NextEvent();
                foreach (Message msg in eventObj.GetMessages())
                {
                    Debug.WriteLineIf(traceM.TraceVerbose, msg);
                    Debug.WriteLineIf(traceM.TraceVerbose, msg.MessageType);
                    if (msg.MessageType.Equals(Bloomberglp.Blpapi.Name.GetName("ReferenceDataResponse")))
                    {
                        if (msg.HasElement(RESPONSE_ERROR))
                        {
                            Element error = msg.GetElement(RESPONSE_ERROR);
                            throw new Exception("Bloomberg Response Error: " + error.GetElementAsString(MESSAGE));
                        }

                        // No response error
                        Element secDataArray = msg.GetElement(SECURITY_DATA);
                        if (secDataArray.HasElement(SECURITY_ERROR))
                        {
                            // security error
                            Element secError = secDataArray.GetElement(SECURITY_ERROR);
                            throw new Exception("Bloomberg Security error:" + secError.GetElementAsString(MESSAGE));
                        }

                        // process securities data
                        Element sData = secDataArray.GetValueAsElement(0); // we know there is only 1 security. Get the first element
                        foreach (Element e in sData.Elements)  // iterate over 'security', 'sequenceNumber' and 'fieldData' 
                        {
                            Debug.WriteLineIf(traceM.TraceVerbose, "Evaluating e=" + e);
                            if (e.Name.ToString() == "fieldData")
                            {
                                result = e.GetElement("PX_LAST").GetValueAsFloat64();
                                timeStamp = e.GetElement("LAST_UPDATE").GetValueAsString();
                                Debug.WriteLineIf(traceM.TraceVerbose, "Processing result=" + result + ", timeStamp=" + timeStamp);
                            }
                        } // end foreach
                    }
                }
                if (eventObj.Type == Event.EventType.RESPONSE)
                {
                    break;
                }
            }
            Debug.Assert(result > 0, "Bloomberg: Unassigned last price for ticker: " + ticker);
            return result;
        }
        #endregion

        #region Private Methods
        /// <summary>Create Bloomberg field string from QuoteType</summary>
        /// <param name="type">QuoteType</param>
        /// <returns>Bloomberg field string</returns>
        private string quoteToBloombergField(QuoteType type)
        {
            switch (type)
            {

                case QuoteType.AdjClose:
                case QuoteType.Close:
                    return "PX_LAST";
                case QuoteType.Open:
                    return "PX_OPEN";
                case QuoteType.Low:
                    return "PX_LOW";
                case QuoteType.High:
                    return "PX_HIGH";
                case QuoteType.Volume:
                    return "PX_VOLUME";
                default:
                    throw new Exception("BloombergLoader: Unrecognized type " + type.ToString());
            }
        }
        #endregion
    }
}


/* 
 * HistoricalDataResponse
 * 
msg.AsElements=HistoricalDataResponse (choice) = {
    securityData = {
        security = USO US Equity
        sequenceNumber = 0
        fieldData[] = {
            fieldData = {
                date = 2010-01-19
                PX_LAST = 38.93
            }
            fieldData = {
                date = 2010-01-20
                PX_LAST = 37.89
            }
        }
    }
}
*/

/*
 * ReferenceDataResponse
 * 
ReferenceDataResponse (choice) = {
    securityData[] = {
        securityData = {
            security = EDC US Equity
            sequenceNumber = 0
            fieldData = {
                PX_LAST = 21.59
                LAST_UPDATE = 19:12:24.000
            }
        }
    }
}

*/